【X68000(Z)アセンブラ講座 第020回 DMAによるCG画像高速描画】 2025/05/29   こんにちは☆ 今回はハードウェアによるCG画像描画の高速化に期待して DMAC(Direct Memory Access Controler / 以後'DMA')を使用し 画像サイズ512x512x8の画像を全速力目指して描画表示するプログラムの一例です。 DMAの転送モードは3つあり、今回は一番簡単な標準モードを使用します。 (今回のサンプルも僕の手元に実機のX68000(10MHz)がないのでX68000Zのみテストしました。 よってX68000ZのDMA処理速度が実機と同じかどうかは解りません。) サンプルプログラムを動かす前に 512×512ピクセル8ビットカラー(256色)のBMP画像ファイルを用意して 'BM_TEST5.bmp'と言うファイル名で保存して下さい。 破線の内側を'BMP512_D.s'と言うファイル名で保存して下さい。 ------------------------------------------------------------------------------------------------ ******************************************************************************** * * アプリ名 : BMP512_D.x * * DMAを使ってCG画像高速描画アプリ * * Ver1.00 * ******************************************************************************** include A:\XC\INCLUDE\DOSCALL.MAC * OSコール用マクロの読み込み include A:\XC\INCLUDE\IOCSCALL.MAC * IOCS(BIOS)コール用マクロの読み込み * システム領域のアドレス cg_palette equ $E82000 * CG用パレットデータの先頭アドレス cg_buffer equ $C00000 * CG-VRAMの先頭アドレス cg_scroll_x equ $E80018 cg_scroll_y equ $E8001a sp_palette equ $E82200 vsync equ $E88001 * %00010000でV-Syncチェック dmaptr equ $e84080 * DMAレジスター2の先頭アドレス * 以下DMAレジスターアドレスの先頭からのオフセット(距離) csr equ 0 cer equ 1 spare1 equ 2 dcr equ 4 ocr equ 5 scr equ 6 ccr_ equ 7 spare2 equ 8 mtc equ 10 mar equ 12 space3 equ 16 dar equ 20 spare4 equ 24 btc equ 26 bar equ 28 spare5 equ 32 spare6 equ 36 niv equ 37 spare7 equ 38 eiv equ 39 spare8 equ 40 mfc equ 41 spare9 equ 42 spare10 equ 44 cpr equ 45 spare11 equ 46 spare12 equ 48 dfc_ equ 49 spare13 equ 50 spare14 equ 54 spare15 equ 56 bfc equ 57 spare16 equ 58 spare17 equ 62 gcr equ 63 .cpu 68000 * CPUのタイプ .data * '.data'以降のデータはデータセクションに配置される file00: dc.b 'BM_TEST5.bmp',0 * 読み込むBMPのファイル名 msg_return: dc.b 13,10,0 * 改行コード('13'+'10') & 終端コード('0') msg01: dc.b 'BMP File Name : ',0 msg02: dc.b 'File Open Error !',13,10,0 msg03: dc.b 'BMP形式のファイルではありません !',13,10,0 msg04: dc.b '8ビットカラー(256色)の画像ではありません !',13,10,0 msg05: dc.b 'CGのサイズが512×512ピクセルではありません !',13,10,0 .even * '.even'以降の偶数アドレスにデータを置く fh: dc.w 0 * ファイルハンドル(ファイル管理番号) viewW: dc.l 0 viewH: dc.l 0 bmp_header_start: * bmp_header_endまでBMPファイルのヘッダーデータ領域 BMFH: * BITMAPFILEHEADER ビットマップファイルヘッダー 14bytes bmph: dc.b 0, 0 * 'B'と'M'ならばBMP形式画像 dc.l 0 * BMPファイルのサイズ dc.w 0 * 予約領域1 dc.w 0 * 予約領域2 dc.l 0 * ファイルヘッダの先頭からビットマップの先頭までの長さ BMIH: * BITMAPINFOHEADER ビットマップインフォヘッダー 40bytes biSize: dc.l 0 * BITMAPINFOHEADER領域のバイト数 biWidth: dc.w 0, 0 * 横ピクセル数 biHeight: dc.w 0, 0 * 縦ピクセル数 biPlanes: dc.w 0 * 1 biBitCount: dc.w 0 * カラーモード(4/8/16/24/32) biCompression: dc.l 0 * BI_RGB biSizeImage: dc.l 0 * イメージの全バイト数 biXPelsPerMeter:dc.l 0 * 水平解像度 biYPelsPerMeter:dc.l 0 * 垂直解像度 biClrUsed: dc.w 0, 0 * 使用されている色の数 biClrImportant: dc.l 0 * 0 bmp_header_end: * BMPファイルのヘッダー領域の終わり .bss * '.bss'以降データバッファ BMPal ds.b 1024 * パレットデータバッファ(4*256byte確保する) bmtemp: ds.b 262144 * ピクセルデータ読み込みバッファ(512*512byte確保する) pic ds.b 524288 * VRAMにDMA転送して表示する画像データ(512*512*2byte) .text * '.text'以降のプログラムはテキストセクションに配置される start: * スタートアドレス * ユーザーモードからスーパーバイザーモードに移行する moveq.l #_B_SUPER,d0 * d0にIOCS(BIOS)機能番号を代入 move.l #0,d1 * 数値0をd1に代入 movea.l d1,a1 * d1をa1にコピー trap #15 * IOCSコール実行 * 画面モードの設定 moveq.l #_CRTMOD,d0 * 画面モードの設定 move.w #8,d1 * 画面モードの番号 : 16 = 768x512x4 / 8 = 512x512x8 trap #15 * 画面をクリアして表示をオンにする moveq.l #_G_CLR_ON,d0 * 画面をクリアして表示をオンにする trap #15 pea msg01 * 文字列データmsg01のアドレスをスタックに積む dc.w _PRINT * 文字列の表示 addq.l #4,sp * spの位置を元に戻す pea file00 * ファイル名の先頭アドレスをスタックに積む dc.w _PRINT addq.l #4,sp pea msg_return * 改行するために改行コードのアドレスをスタックに積む dc.w _PRINT addq.l #4,sp bsr load_bmp * BMPファイル読み込みルーチンの呼び出し bsr set_palette * CGパレット設定ルーチンの呼び出し * ファイルから読み込んだ8ビットの画像データをX68Kの16ビットピクセル幅に変換 bsr init_pic loop: * DMAによる画像高速描画 * 座標( 0, 0)に512*128pxサイズの画像描画 lea pic,a0 movea.l #$C00000,a1 move.w #$8000,d0 bsr dma_setup bsr dma_start bsr dma_wait_complete * 座標( 0,128)に512*128pxサイズの画像描画 lea pic,a0 add.l #1024*128,a0 movea.l #$C20000,a1 move.w #$8000,d0 bsr dma_setup bsr dma_start bsr dma_wait_complete * 座標( 0,256)に512*128pxサイズの画像描画 lea pic,a0 add.l #1024*256,a0 movea.l #$C40000,a1 move.w #$8000,d0 bsr dma_setup bsr dma_start bsr dma_wait_complete * 座標( 0,384)に512*128pxサイズの画像描画 lea pic,a0 add.l #1024*384,a0 movea.l #$C60000,a1 move.w #$8000,d0 bsr dma_setup bsr dma_start bsr dma_wait_complete * ESCキーのチェック moveq.l #_BITSNS,d0 moveq.l #0,d1 trap #15 and.b #%00000010,d0 tst.b d0 bne end bra loop end: moveq.l #_CRTMOD,d0 * 画面モードの設定 move.w #16,d1 * 画面モードの番号 : 16 = 768x512x4 / 8 = 512x512x8 trap #15 * スーパーバイザーモードからユーザーモードに戻る moveq.l #_B_SUPER,d0 move.l SP,a1 trap #15 * アプリ終了 dc.w _EXIT * OSコール実行 : _EXIT = プログラムの終了 load_bmp: * BMPファイルのデータを読み込むルーチン * File Open (ファイルアクセス開始) move.w #0,-(sp) * アクセスモードを指定。0 = 読み込みモード/1 = 書き込みモード pea file00 * ファイル名が書かれた部分の先頭アドレスをスタックに積む dc.w _OPEN * OSコールの実行 : ファイルのオープン addq.l #6,sp * SPの位置を元に戻す tst.l d0 * もしd0がプラスなら、 bpl load_bmp_0 * ラベルload_bmp_0に飛ぶ。 pea msg02 * エラーメッセージ dc.w _PRINT addq.l #4,sp dc.w _EXIT * OSコール実行 : エラーによりソフトウェアの終了 load_bmp_0: move.w d0,fh * _OPENを実行した後にd0にファイルハンドルが入っているので * d0のファイル管理番号をfhに保存する * BMFH read move.l #14,-(sp) * 読み込みバイト数をスタックに積む pea BMFH * アドレスBMFHをスタックに積む move.w fh,-(sp) * ファイルハンドルをスタックに積む dc.w _READ * OSコール実行 : ファイルの読み込み add.l #10,sp * SPの位置を元に戻す cmp.w #'BM',bmph * BMPファイルヘッダーが'BM'か比較する beq load_bmp_1 * 'BM'ならload_bmp_1に飛ぶ pea msg03 dc.w _PRINT addq.l #4,sp dc.w _EXIT * 'BM'以外なのでアプリ終了 load_bmp_1: * BMIH read move.l #40,-(sp) * 40byteのファイルヘッダー pea BMIH move.w fh,-(sp) dc.w _READ * ファイル読み込みのOSコールを実行する lea 10(sp),sp move.w biBitCount,d0 ror.w #8,d0 * d0.wの中身の2進数データを右に8ビットローテート(Intel対策) cmp.w #8,d0 * 8Bitカラー(256色)か比較する beq load_bmp_2 pea msg04 dc.w _PRINT addq.l #4,sp dc.w _EXIT load_bmp_2: * 画像の横ピクセル数チェック move.w biWidth,d0 ror.w #8,d0 * Intel対策(2バイトのデータが左右逆なのでローテートで元に戻す) ext.l d0 * 16ビットのデータをプラスマイナス保ったまま32ビットに拡張する cmp.w #512,d0 * d0.wと数値768を比較する beq load_bmp_3 * branch equal / 比較結果が同じならアドレス'load_bmp_3'に飛ぶ pea msg05 dc.w _PRINT addq.l #4,sp dc.w _EXIT load_bmp_3: move.l d0,viewW * 画像の縦ピクセル数チェック move.w biHeight,d1 ror.w #8,d1 * Intel対策のためバイトデータ逆転させる ext.l d1 cmp.w #512,d1 beq load_bmp_4 pea msg05 dc.w _PRINT addq.l #4,sp dc.w _EXIT load_bmp_4: move.l d1,viewH * BMP パレットの読み込み move.l #1024-4,-(sp) * パレットデータのサイズをスタックに積む pea BMPal move.w fh,-(sp) dc.w _READ lea 10(sp),sp * BMP ピクセルデータの読み込み move.l #262144,-(sp) * ピクセルデータの全てのサイズをスタックに積む pea bmtemp * bmp = 読み込んだピクセルデータを配置するアドレス move.w fh,-(sp) dc.w _READ lea 10(sp),sp * File Close (ファイルアクセス終了) move.w fh,-(sp) dc.w _CLOSE addq.l #2,sp rts * サブルーチンの最後に配置(呼出元にリターンし次の命令から実行再開) * RGBQUAD { Blue8, Green8, Red8, Reserved8 } = %RRRRRRRR_GGGGGGGG_BBBBBBBB_AAAAAAAA * palette { Green5, Red5, Blue5, Alpha1 } = %GGGGGRRR_RRBBBBB0 * Set CG Palette set_palette: movea.l #cg_palette,a0 * CG用パレットレジスターアドレス lea BMPal,a1 * パレットデータバッファアドレス moveq.l #0,d7 set_palette_1: move.b (a1),d0 * a1が示すアドレスからd0にバイトデータを読み込む * ↓32ビット色の青色成分8ビットのうち上位5ビットを抜き取り16ビット色の青色成分として使う lsr.w #2,d0 * 青色ビットデータの位置を16ビット色内の青色位置に右シフト and.w #%0000000000111110,d0 * 16ビット色の青色成分以外のビットを0にする move.b 1(a1),d1 * a1に1を足したアドレスからd1にバイトデータを読み込む * ↓32ビット色の緑色成分8ビットのうち上位5ビットを抜き取り16ビット色の緑色成分として使う lsl.w #8,d1 * 緑色ビットデータの位置を16ビット色内の緑色位置に左シフト and.w #%1111100000000000,d1 * 16ビット色の緑色成分以外のビットを0にする move.b 2(a1),d2 * a1に2を足したアドレスからd2にバイトデータを読み込む * ↓32ビット色の赤色成分8ビットのうち上位5ビットを抜き取り16ビット色の赤色成分として使う lsl.w #3,d2 * 赤色ビットデータの位置を16ビット色内の赤色位置に左シフト and.w #%0000011111000000,d2 * 16ビット色の赤色成分以外のビットを0にする * 青赤緑のそれぞれの色成分を論理和命令で一つの16ビットカラーに完成させる or.w d1,d0 or.w d2,d0 * 完成した色データをパレットメモリーに書き込んで更新する move.w d0,(a0)+ * 16ビットデータを書き込んだ後にa0に2を足す * 次の変換元色データを取り出す前にa1のアドレスを更新しておく addq.l #4,a1 addq.w #1,d7 * 色番号を次の番号に更新する cmp.w #256,d7 * 現在の色番号と16を比較する blt set_palette_1 * 現在の色番号が16未満ならアドレスset_palette_1に飛ぶ rts * 呼出元に戻る init_pic: lea bmtemp,a0 lea pic,a1 move.l #0,d7 init_pic_1: move.b (a0)+,d0 move.w d0,(a1)+ addq.l #1,d7 cmp.l #524288,d7 blt init_pic_1 rts * DMA転送が終わるまで待つ dma_wait_complete: movem.l d0/a0,-(sp) movea.l #dmaptr,a0 dma_wait_complete_1: move.b (a0),d0 and.b #$90,d0 cmp.b #0,d0 beq dma_wait_complete_1 movem.l (sp)+,d0/a0 rts * DMA転送スタート dma_start: move.l a0,-(sp) movea.l #dmaptr,a0 or.b #$80,ccr_(a0) move.l (sp)+,a0 rts * DMA転送開始前の設定 dma_setup: * in a0.l = データ転送元アドレス * in a1.l = データ転送先アドレス * in d0.w = 4バイトデータ(32Bit)の転送回数 move.l a6,-(sp) movea.l #dmaptr,a6 move.b #$ff,csr(a6) * フラグのクリアー move.b #$08,dcr(a6) * 16Bitポート move.b #%00_10_0001,ocr(a6) * 転送=ロングワード(32Bit)単位 / オートリクエスト最大速度 move.b #%0000_01_01,scr(a6) * 転送元も転送先もレジスタの値は増加 move.b #$00,ccr_(a6) * デフォルト move.b #$000000_11,cpr(a6) * チャンネルプライオリティ : もっとも低い move.b #$05,mfc(a6) * スーパーバイザーデータ move.b #$05,dfc_(a6) * スーパーバイザーデータ move.w d0,mtc(a6) * move.l a0,mar(a6) move.l a1,dar(a6) move.l (sp)+,a6 rts * [EOF] ------------------------------------------------------------------------------------------------ コマンドプロンプトから、 A>as BMP512_D.s [Enter] A>lk BMP512_D.o [Enter] A>BMP512_D.x [Enter] のように入力すると DMAによって用意した画像ファイルが表示されます。 ESCキーで終了します。 [ プログラムの解説 ] 今回は512x512(256色)の画面モードに設定して その全画面と同じピクセル数の画像データを全速力でCG-VRAMに転送する事で画像を表示します。 一度の転送幅を32ビット(2ピクセル同時転送)に設定してあるのである程度は速いはずです。 dma_setupルーチンを呼ぶ時にd0.wに16ビット値($0001〜$ffff/$0000は代入禁止)で転送回数を指定します。 今回は一度の転送サイズが4バイトなので1回のdma_startルーチンの呼び出しで 最大[ 4 × $ffff = 262140 ]バイトのデータ転送ができます。 データ転送先VRAMの容量は[ 512 × 512 × 2 = 524288 ]バイトです。 ここまで読んできてdma_startを2回呼べば1画面描画できそうな気になりますが実はそれは間違いです。 VRAMの容量524288バイトの半分は262144バイトであり dma_start呼び出し1回あたり4バイトの未転送が発生して不完全な画像描画になります。 理論的に転送回数に1を足して$10000回の転送回数にすれば過不足なく画像描画できるはずですが 物理的にd0.wに$10000は代入できません。 さて、どう対処すべきか。 絶望している暇なんかない。 答えは簡単に解りました☆ 転送回数に$10000の半分の$8000を代入して dma_startルーチンを倍の4回呼べば完璧だ!! 犠牲としては普通の人間には解らないくらいほんの少しの命令実行時間が増えるだけだ!! その結果が無事完成した今回のサンプルプログラムです☆ 実は今回のDMAのルーチンは X68000の専門書「Inside X68000」内のDMAのC言語サンプルを僕がアセンブリ言語版に書き換えて 更に使い易くプログラムを書き換えたものです(笑) 書き換えている最中にやたらと$ffffが気になっていたのでマークしていたのは正解でした♪ と言う事で、今回は以上で終了です。 お疲れ様でした(^^) [EOF]